package com.qf.qfarticle.service.impl;


import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qf.qfarticle.mapper.ApArticleConfigMapper;
import com.qf.qfarticle.mapper.ApArticleContentMapper;
import com.qf.qfarticle.mapper.ApArticleMapper;
import com.qf.qfarticle.service.ApArticleService;
import com.qf.qfleadnewscommons.minio.MinioOperator;
import com.qf.qfleadnewsfeignapi.wemedia.WmNewsApi;
import com.qf.qfleadnewsmodel.article.dtos.ArticleHomePageDto;
import com.qf.qfleadnewsmodel.article.pojos.ApArticle;
import com.qf.qfleadnewsmodel.article.pojos.ApArticleConfig;
import com.qf.qfleadnewsmodel.article.pojos.ApArticleContent;
import com.qf.qfleadnewsmodel.commons.consts.RedisConst;
import com.qf.qfleadnewsmodel.commons.dtos.ResponseResult;
import com.qf.qfleadnewsmodel.wemedia.pojos.WmNews;
import freemarker.template.Configuration;
import freemarker.template.Template;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
@Slf4j
public class ApAriticleServiceImpl2 extends ServiceImpl<ApArticleMapper, ApArticle> implements ApArticleService {

    @Autowired
    private WmNewsApi wmNewsApi;

    @Autowired
    private ApArticleConfigMapper apArticleConfigMapper;

    @Autowired
    private ApArticleContentMapper apArticleContentMapper;

    @Autowired
    private MinioOperator minioOperator;

    /**
     * FreeMarker的核心配置
     */
    @Autowired
    private Configuration configuration;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public ResponseResult load(ArticleHomePageDto articleHomePageDto) {
        //List<ApArticle> apArticles = baseMapper.loadHome(articleHomePageDto);

        String key = RedisConst.ARTICLE_HOT_CACHE_PREFIX + articleHomePageDto.getTag();
        String articlesJson = redisTemplate.opsForValue().get(key);

        List<ApArticle> apArticles = null;
        if (StringUtils.isNotBlank(articlesJson)){
            apArticles = JSON.parseArray(articlesJson,ApArticle.class);
        }

        if (apArticles == null || apArticles.size() == 0) {
            //从数据库查询，查到后，放入到redis缓存 (缓存问题：击穿、穿透、雪崩、双写一致性)
            apArticles = baseMapper.loadHome(articleHomePageDto);
        }
        return ResponseResult.okResult(apArticles);
    }


    @Override
    public ResponseResult loadMoreOrNew(ArticleHomePageDto articleHomePageDto) {
        List<ApArticle> apArticles = baseMapper.loadHome(articleHomePageDto);
        return ResponseResult.okResult(apArticles);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveAticle(WmNews wmNews) throws Exception {
        //向article表中插入记录
        ApArticle apArticle = new ApArticle();
        BeanUtils.copyProperties(wmNews,apArticle);
        apArticle.setId(null);

        //设置author_id
        apArticle.setAuthorId(Long.valueOf(wmNews.getUserId()));
        //todo: 设置author_name
        String authorName = wmNewsApi.findAuthorNameByAuthorId(wmNews.getUserId());
        apArticle.setAuthorName(authorName);
        //todo: 设置channel_name
        String channelName = wmNewsApi.findChannelNameByChannelId(wmNews.getChannelId());
        apArticle.setChannelName(channelName);

        //设置layout
        apArticle.setLayout(wmNews.getType());
        //设置同步状态
        apArticle.setSyncStatus(true);

        //保存article
        save(apArticle);

        //向config表中插入记录
        ApArticleConfig apArticleConfig = new ApArticleConfig(apArticle.getId());
        apArticleConfigMapper.insert(apArticleConfig);

        //向content表中插入记录
        ApArticleContent apArticleContent = new ApArticleContent();
        apArticleContent.setArticleId(apArticle.getId());
        apArticleContent.setContent(wmNews.getContent());
        apArticleContentMapper.insert(apArticleContent);

        //页面静态化 -- 将静态化后的url存入到数据库
        proccessStaticUrl(apArticleContent,apArticle);
        //回填articleid并修改状态为已发布
        wmNewsApi.fillArticleId(wmNews.getId().toString(),apArticle.getId().toString());
    }

    /**
     * 对文章内容进行静态化处理
     * @param apArticleContent
     * @param apArticle
     */
    private void proccessStaticUrl(ApArticleContent apArticleContent, ApArticle apArticle) throws Exception {

        log.info("开始静态化处理.......................");

        //按照模板要求，组织数据
        String content = apArticleContent.getContent();
        List<Map> contentMap = JSON.parseArray(content, Map.class);

        Map data = new HashMap();
        data.put("content",contentMap);

        Template template = configuration.getTemplate("article.ftl");

        /**
         * todo：
         *      思考：这里我是将内容数据直接写入到内存输出流中，有什么风向？？？？
         *        如果自媒体端发布文章的并发很高，而且文章内容的数据很大，可能导致文章服务内存开销过大。最终可能导致服务因为内存不足宕机
         *        解决方案：不要存入内存，存入服务器的临时文件，静态化完成后，将临时文件删除。
         *          Easyexcel中就是这样解决内存溢出问题的。
         */
        try(StringWriter writer = new StringWriter()){
            //将数据写入到内存中保存
            template.process(data,writer);

            //将得到的html数据上传minio服务器
            //将内存中的字符输出流的数据，转换成字节数组，并构建一个对应字节输入流
            InputStream is = new ByteArrayInputStream(writer.toString().getBytes(StandardCharsets.UTF_8));
            String url = minioOperator.uploadHtml(is);

            log.info("================上传后的静态化url: {}",url);

            //将静态化的url填充到article表中
            apArticle.setStaticUrl(url);
            updateById(apArticle);
        }
    }
}
